/*
 * Funny Language - a free style programming language.
 * Copyright (C) 2015 by fanguangping (fanguangping@163.com)
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include "runner_translate.h"

char* __wrap_macro_function(char* functionName, int argc, Statement **argv);

char* __wrap_macro_function(char* functionName, int argc, Statement **argv) {
    check_expected(functionName);
    check_expected(argc);
    check_expected_ptr(argv);
    return (char*)mock();
}

static Mapping* construct_mapping(String statement, String name, String function, String mapping, String run, String eval) {
    Mapping* m = alloc_mapping();
    m->isMacro = FALSE;
    m->source = statement;
    m->sourceStatement = parse(statement);
    m->sys = alloc_sys_block();
    m->sys->name = name;
    m->sys->function = function;
    m->sys->mapping = mapping;
    m->sys->run = run;
    m->sys->eval = eval;
    return m;
}

static void add_replacement(Queue* queue, String argName, Statement* statement) {
    KeyValueNode* node = alloc_map_node();
    node->key = argName;
    node->value = statement;
    enqueue(queue, node);
}

static void test_eval_sys_beginlex() {
    String statement = "{define lex {orders}}";
    String name = "beginlex";
    String function = "NIL";
    String mapping = "NIL";
    String run = "LEX[{orders}]";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *orders = parse("{\"boolean > character > string > integer > fraction > decimal > complex > symbol\"}");
    Statement **the_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *the_argv = orders;
    *(the_argv + 1) = NULL;
    add_replacement(queue, "orders", orders);

    expect_string(__wrap_macro_function, functionName, "LEX");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, the_argv, 1);
    will_return(__wrap_macro_function, "");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
}

static void test_eval_sys_defrule() {
    String statement = "{define lex rule {token} -> {pattern}}";
    String name = "defrule";
    String function = "NIL";
    String mapping = "NIL";
    String run = "RULE[{token},{pattern}]";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *token = parse("{boolean}");
    Statement *pattern = parse("{\"<bool> :: (lex \\\"Boolean\\\" \\\"$1\\\")\"}");
    Statement **the_argv = (Statement **)malloc(3 * sizeof(Statement *));
    *the_argv = token;
    *(the_argv + 1) = pattern;
    *(the_argv + 2) = NULL;
    add_replacement(queue, "token", token);
    add_replacement(queue, "pattern", pattern);

    expect_string(__wrap_macro_function, functionName, "RULE");
    expect_value(__wrap_macro_function, argc, 2);
    expect_memory(__wrap_macro_function, argv, the_argv, 2);
    will_return(__wrap_macro_function, "");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
}

static void test_eval_sys_defgroup() {
    String statement = "{define lex group {token} -> {pattern}}";
    String name = "defgroup";
    String function = "NIL";
    String mapping = "NIL";
    String run = "GROUP[{token},{pattern}]";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *token = parse("{integer}");
    Statement *pattern = parse("{\"(<sign><digit>+) :: (lex-integer $)\"}");
    Statement **the_argv = (Statement **)malloc(3 * sizeof(Statement *));
    *the_argv = token;
    *(the_argv + 1) = pattern;
    *(the_argv + 2) = NULL;
    add_replacement(queue, "token", token);
    add_replacement(queue, "pattern", pattern);

    expect_string(__wrap_macro_function, functionName, "GROUP");
    expect_value(__wrap_macro_function, argc, 2);
    expect_memory(__wrap_macro_function, argv, the_argv, 2);
    will_return(__wrap_macro_function, "");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
}

static void test_eval_sys_deftoken() {
    String statement = "{define lex token {token} -> {pattern}}";
    String name = "deftoken";
    String function = "NIL";
    String mapping = "NIL";
    String run = "TOKEN[{token},{pattern}]";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *token = parse("{bool}");
    Statement *pattern = parse("{\"#[tf]\"}");
    Statement **the_argv = (Statement **)malloc(3 * sizeof(Statement *));
    *the_argv = token;
    *(the_argv + 1) = pattern;
    *(the_argv + 2) = NULL;
    add_replacement(queue, "token", token);
    add_replacement(queue, "pattern", pattern);

    expect_string(__wrap_macro_function, functionName, "TOKEN");
    expect_value(__wrap_macro_function, argc, 2);
    expect_memory(__wrap_macro_function, argv, the_argv, 2);
    will_return(__wrap_macro_function, "");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
}

static void test_eval_sys_deftpl() {
    String statement = "{define template {name}: {template} as {statement}}";
    String name = "deftpl";
    String function = "FUNCNAME[{statement}]";
    String mapping = "{template} > {statement}";
    String run = "NIL";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *name_statement = parse("{or}");
    Statement *template = parse("{{x} or {y}}");
    Statement *target_statement = parse("{(or {x} {y})}");
    Statement **the_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *the_argv = target_statement;
    *(the_argv + 1) = NULL;
    add_replacement(queue, "name", name_statement);
    add_replacement(queue, "template", template);
    add_replacement(queue, "statement", target_statement);

    mappingTrie = init_trie(calc_mapping_prefix, calc_statement_prefix);
    expect_string(__wrap_macro_function, functionName, "FUNCNAME");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, the_argv, 1);
    will_return(__wrap_macro_function, "or");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
    Queue *q = get_mappings(parse("{{arg1} or {arg2}}"));
    assert_non_null(q);
    Mapping* the_mapping = (Mapping*)(q->head->element);
    assert_string_equal(the_mapping->functionName, "or");
    assert_string_equal(the_mapping->source, "{{x} or {y}}");
    assert_string_equal(the_mapping->target, "{(or {x} {y} )}");
}

static void test_eval_sys_deftpl_loop() {
    String statement = "{define template {cond}: {case [when {when_clause} then {then_clause},] else {else_clause}} as {(cond [({when_clause} {then_clause})] (else {else_clause}))}}";
    String name = "deftpl";
    String function = "FUNCNAME[{statement}]";
    String mapping = "{template} > {statement}";
    String run = "NIL";
    String eval = "NIL";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *name_statement = parse("{cond}");
    Statement *template = parse("{case [when {when_clause} then {then_clause},] else {else_clause}}");
    Statement *target_statement = parse("{(cond [({when_clause} {then_clause})] (else {else_clause}))}");
    Statement **the_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *the_argv = target_statement;
    *(the_argv + 1) = NULL;
    add_replacement(queue, "name", name_statement);
    add_replacement(queue, "template", template);
    add_replacement(queue, "statement", target_statement);

    mappingTrie = init_trie(calc_mapping_prefix, calc_statement_prefix);
    expect_string(__wrap_macro_function, functionName, "FUNCNAME");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, the_argv, 1);
    will_return(__wrap_macro_function, "cond");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "");
    Queue *q = get_mappings(parse("{case when {{a}>{0}} then {1}, when {{a}<{0}} then {-1}, else {0}}"));
    assert_non_null(q);
    Mapping* the_mapping = (Mapping*)(q->head->element);
    assert_string_equal(the_mapping->functionName, "cond");
    assert_string_equal(the_mapping->source, "{case [when {when_clause} then {then_clause}] else {else_clause}}");
    assert_string_equal(the_mapping->target, "{(cond [( {when_clause} {then_clause} )] (else {else_clause} ))}");
}

static void test_eval_sys_defvar() {
    String statement = "{define {var} as {val}}";
    String name = "defvar";
    String function = "NIL";
    String mapping = "NIL";
    String run = "NIL";
    String eval = "(define EXTRACT[{var}] MATCH[{val}])";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *var = parse("{abc}");
    Statement *val = parse("{123}");
    Statement **extract_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *extract_argv = var;
    *(extract_argv + 1) = NULL;
    Statement **match_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *match_argv = val;
    *(match_argv + 1) = NULL;
    add_replacement(queue, "var", var);
    add_replacement(queue, "val", val);

    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "abc");

    expect_string(__wrap_macro_function, functionName, "MATCH");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, match_argv, 1);
    will_return(__wrap_macro_function, "123");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "(define abc 123)");
}

static void test_eval_sys_defvar_expression() {
    String statement = "{define {var} as {val}}";
    String name = "defvar";
    String function = "NIL";
    String mapping = "NIL";
    String run = "NIL";
    String eval = "(define EXTRACT[{var}] MATCH[{val}])";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *var = parse("{abc}");
    Statement *val = parse("{{1}+{2}}");
    Statement **extract_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *extract_argv = var;
    *(extract_argv + 1) = NULL;
    Statement **match_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *match_argv = val;
    *(match_argv + 1) = NULL;
    add_replacement(queue, "var", var);
    add_replacement(queue, "val", val);

    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "abc");

    expect_string(__wrap_macro_function, functionName, "MATCH");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, match_argv, 1);
    will_return(__wrap_macro_function, "(+ 1 2)");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "(define abc (+ 1 2))");
}

// {define function {qsum}: {the sum of squares {a} and {b}} as {{{a}*{a}}+{{b}*{b}}}}
static void test_eval_sys_defun() {
    String statement = "{define function {name}: {function} as {statement}}";
    String name = "defun";
    String function = "EXTRACT[{name}]";
    String mapping = "{function} > \\{(EXTRACT[{name}] TARGET[{function}])\\}";
    String run = "NIL";
    String eval = "(define (EXTRACT[{name}] ARGLIST[{function}]) MATCH[{statement}])";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *name_statement = parse("{qsum}");
    Statement *function_statement = parse("{the sum of squares {a} and {b}}");
    Statement *target_statement = parse("{{{a}*{a}}+{{b}*{b}}}");
    Statement **extract_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *extract_argv = name_statement;
    *(extract_argv + 1) = NULL;
    Statement **target_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *target_argv = function_statement;
    *(target_argv + 1) = NULL;
    Statement **arglist_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *arglist_argv = function_statement;
    *(arglist_argv + 1) = NULL;
    Statement **match_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *match_argv = target_statement;
    *(match_argv + 1) = NULL;
    add_replacement(queue, "name", name_statement);
    add_replacement(queue, "function", function_statement);
    add_replacement(queue, "statement", target_statement);

    mappingTrie = init_trie(calc_mapping_prefix, calc_statement_prefix);
    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "qsum");

    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "qsum");

    expect_string(__wrap_macro_function, functionName, "TARGET");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, target_argv, 1);
    will_return(__wrap_macro_function, "{a} {b}");

    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "qsum");

    expect_string(__wrap_macro_function, functionName, "ARGLIST");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, arglist_argv, 1);
    will_return(__wrap_macro_function, "a b");

    expect_string(__wrap_macro_function, functionName, "MATCH");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, match_argv, 1);
    will_return(__wrap_macro_function, "(+ (* a a) (* b b))");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "(define (qsum a b) (+ (* a a) (* b b)))");
    Queue *q = get_mappings(parse("{the sum of squares {2} and {3}}"));
    assert_non_null(q);
    Mapping* the_mapping = (Mapping*)(q->head->element);
    assert_string_equal(the_mapping->functionName, "qsum");
    assert_string_equal(the_mapping->source, "{the sum of squares {a} and {b}}");
    assert_string_equal(the_mapping->target, "{(qsum {a} {b})}");
}

static void test_eval_sys_lambda() {
    String statement = "{lambda {args}: {expression}}";
    String name = "lambda";
    String function = "NIL";
    String mapping = "NIL";
    String run = "NIL";
    String eval = "(lambda (EXTRACT[{args}]) MATCH[{expression}])";
    Mapping* m = construct_mapping(statement, name, function, mapping, run, eval);
    Queue *queue = init_queue();
    Statement *args = parse("{a b c}");
    Statement *expression = parse("{{a}+{b}+{c}}");
    Statement **extract_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *extract_argv = args;
    *(extract_argv + 1) = NULL;
    Statement **match_argv = (Statement **)malloc(2 * sizeof(Statement *));
    *match_argv = expression;
    *(match_argv + 1) = NULL;
    add_replacement(queue, "args", args);
    add_replacement(queue, "expression", expression);

    expect_string(__wrap_macro_function, functionName, "EXTRACT");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, extract_argv, 1);
    will_return(__wrap_macro_function, "a b c");

    expect_string(__wrap_macro_function, functionName, "MATCH");
    expect_value(__wrap_macro_function, argc, 1);
    expect_memory(__wrap_macro_function, argv, match_argv, 1);
    will_return(__wrap_macro_function, "(+ a b c)");

    String r = eval_sys(m, queue);
    assert_string_equal(r, "(lambda (a b c) (+ a b c))");
}

static void test_eval_sys_macro() {
    String statement = "{define macro {template} as {statement}}";
    String name = "defmacro";
    String function = "NIL";
    String mapping = "{template} > {statement}";
    String run = "NIL";
    String eval = "NIL";

}

static void test_eval_sys_macro_loop() {
    String statement = "{define macro {template} as {statement}}";
    String name = "defmacro";
    String function = "NIL";
    String mapping = "{template} > {statement}";
    String run = "NIL";
    String eval = "NIL";

}


int main(int argc, char **argv) {
    const struct CMUnitTest tests[] = {
            cmocka_unit_test(test_eval_sys_beginlex),
            cmocka_unit_test(test_eval_sys_defrule),
            cmocka_unit_test(test_eval_sys_defgroup),
            cmocka_unit_test(test_eval_sys_deftoken),
            cmocka_unit_test(test_eval_sys_deftpl),
            cmocka_unit_test(test_eval_sys_deftpl_loop),
            cmocka_unit_test(test_eval_sys_defvar),
            cmocka_unit_test(test_eval_sys_defvar_expression),
            cmocka_unit_test(test_eval_sys_defun),
            cmocka_unit_test(test_eval_sys_lambda),
    };
    return cmocka_run_group_tests(tests, setup_translate, teardown_translate);
}
